##############################################################################
##
## Get-PsSpecCmdletDesignerSpecHtml.ps1
##
## Generate an HTML view of a cmdlet spec.
##
## ie:
##
##  PS >Get-PsSpecCmdletDesignerSpecHtml.ps1 MyProject MyCmdlet > c:\temp\MyCmdlet.html
##
##############################################################################

param(
  $projectName = $(throw "Please specify a project name."),
  $cmdletName = $(throw "Please specify a cmdlet name."),
  $serviceUri = "http://TODO_WEBSERVICE_HOST/cmdletdesigner/Cmdlet.asmx"
  )

## Load the System.Web assembly for HTML encoding
[void] [Reflection.Assembly]::LoadWithPartialName("System.Web")

function Main
{
    $cmdlet = Get-PsSpecCmdlet -ProjectName $projectName -Name $cmdletName -ServiceUri $serviceUri

    ## Generate a here-string that contains the Short Description, Long Description, Overview,
    ## and Implementation Details section
@"
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>$($cmdlet.Verb + "-" + $cmdlet.Noun)</title>
<style type="text/css"> <!--
	body
		{ font-family: Calibri; }
	.description
		{ margin-top: -1em; }
	.subheader
		{ font-weight:bold; margin-top:0.4em; }	
	.label
		{ font-weight:bold; font-size:80%; }
	.value
		{ font-size:80%; }
    .offset
        { font-size:80%; color:#999999 }
	.realName
		{ margin-top: -1.0em; margin-left: 1em; font-style:italic; color:#666666; font-size:95%; font-weight:normal; }

		{ }
	.code
		{ font-family: Courier New; font-size:70%; }
	.tableHeader
		{ margin-bottom:0.1em; }
	h1
		{ margin-bottom: -0.3em; }
	h2
		{ color:#365F91; padding-bottom: 10px; padding-top: 20px; }
	h3
		{ color:#365F91; padding-bottom: 10px; padding-top: 10px; font-size:120%; }
        td
                { border: 1px solid black; }
	tr
		{ vertical-align:top; background:#FFFFFF; } 
	th
		{ text-align:left; font-weight:bold; background:#EEEEEE; }
	table
		{ background:#000000; width:100%; border: 1px solid black; border-collapse:collapse }
--> </style>
</head>

<body>
<h1>$($cmdlet.Verb + "-" + $cmdlet.Noun)</h1>
<p><a  class="offset" href="<VIEWANOTHER_REPLACEMENT>">[View another Cmdlet]</a></p>

<h2>Short Description</h2>
<p class="description">$((HtmlEncode $cmdlet.ShortDescription) -replace "`n","<br />`r`n")</p>

<h2>Long Description</h2>
<p class="description">$((HtmlEncode $cmdlet.LongDescription) -replace "`n","<br />`r`n")</p>

<h2 class="tableHeader">Overview</h2>
<table cellspacing="0" cellpadding="4">
  <tr>
    <th colspan="2">Basic Details</th>
  </tr>
  <tr>
    <td width="20%"><p class="label">Verb</p></td>
    <td width="80%"><p class="value">$($cmdlet.Verb)</p></td>
  </tr>
  <tr>
    <td><p class="label">Noun</p></td>
    <td><p class="value">$($cmdlet.Noun)</p></td>
  </tr>
  <tr>
    <td><p class="label">Snapin</p></td>
    <td><p class="value">$($cmdlet.Snapin)</p></td>
  </tr>
  <tr>
    <td><p class="label">Project</p></td>
    <td><p class="value">$($cmdlet.Project)</p></td>
  </tr>
  <tr>
    <td><p class="label">Notes</p></td>
    <td><p class="value">$((HtmlEncode $cmdlet.Notes) -replace "`n","<br />`r`n")</p></td>
  </tr>
</table>

<br />

<table   cellspacing="0" cellpadding="4">
  <tr>
    <th colspan="2">Syntax</th>
  </tr>
  $(
    ## Go through the parameter set syntax entries, and generate rows for each
    foreach($syntaxEntry in GenerateSyntax $projectName $cmdletName $serviceUri)
    {
        $encodedEntry = HtmlEncode $syntaxEntry
        $syntaxEntry = ""
        $syntaxEntry += '  <tr>' + "`r`n"
        $syntaxEntry += '    <td colspan="2"><p class="code">' + $encodedEntry + '</p></td>' + "`r`n"
        $syntaxEntry += '  </tr>' + "`r`n"
        $syntaxEntry
    }
    )
</table>

<br />

<table   cellspacing="0" cellpadding="4">
  <tr>
    <th colspan="2">Spec Examples</th>
  </tr>
  <tr>
    <td colspan="2"><p class="code">$((HtmlEncode $cmdlet.SpecExamples) -replace "`n","<br />`r`n")</p></td>
  </tr>
</table>

<h2 class="tableHeader">Implementation Details</h2>
<table cellspacing="0" cellpadding="4">
  <tr>
    <th colspan="2">Cmdlet Implementation Details</th>
  </tr>
  <tr>
    <td width="20%"><p class="label">Modifies or Changes Data<br />
        <span class="realName">[SupportsShouldProcess]</span></p></td>
    <td width="80%"><p class="value">$($cmdlet.SupportsShouldProcess)</p></td>
  </tr>
  <tr>
    <td><p class="label">Output Object</p></td>
    <td><p class="value">$($cmdlet.OutputObject)</p></td>
  </tr>
  <tr>
    <td><p class="label">Output Object Description</p></td>
    <td><p class="value">$((HtmlEncode $cmdlet.OutputObjectDescription) -replace "`n","<br />`r`n")</p></td>
  </tr>
  <tr>
    <td><p class="label">Related To</p></td>
    <td><p class="value">$($cmdlet.RelatedTo)</p></td>
  </tr>
</table>

<h2 class="tableHeader">Parameters</h2>
$(GenerateParameters $projectName $cmdletName $serviceUri)
</body>
</html>
"@

}

## Generates the parameter-set syntax entries for the spec cmdlet.
## This uses the same logic as the syntax you see from the output of (Get-Command <CmdletName).Definition
function GenerateSyntax($projectName, $cmdletName, $serviceUri)
{
    ## Get all of the validation entries (parameter set entries) that apply to the cmdlet's parameters
    $allParameterSetEntries = @(Get-PsSpecCmdletParameterSetEntry -ProjectName $projectName -CmdletName $cmdletName -ServiceUri $serviceUri)
    $commonParameterSetEntries = @($allParameterSetEntries | Where-Object { -not $_.ParameterSetName })

    $parameterSets = @($allParameterSetEntries | Sort-Object -Unique ParameterSetName | % { $_.ParameterSetName} )

    ## For all parameter set entries in a specific parameter set name
    foreach($parameterSet in $parameterSets)
    {
        $syntaxString = "$cmdletName"
        $sortedParameterSetEntries = @()

        ## Add the entries in a specific parameter set
        if($parameterSet)
        {
            $sortedParameterSetEntries += @($allParameterSetEntries | Where-Object { $_.ParameterSetName -eq $parameterSet })
            $sortedParameterSetEntries = @($sortedParameterSetEntries | 
                Sort-Object { if($_.Position -is [Int]) { $_.Position } else { [Int32]::MaxValue } })
        }
        else
        {
            ## If this is the shared parameter set, and we have other parameter sets,
            ## break
            if($parameterSets.Count -gt 1)
            {
                continue
            }
        }

        ## And add the entries shared by all parameter sets
        $sortedCommonParameterSetEnties = @($commonParameterSetEntries | 
            Sort-Object { if($_.Position -is [Int]) { $_.Position } else { [Int32]::MaxValue } })
        if($sortedCommonParameterSetEnties.Count -gt 0)
        {
            $sortedParameterSetEntries += $sortedCommonParameterSetEnties
        }

        ## Go through the sorted entries
        foreach($parameterSetEntry in $sortedParameterSetEntries)
        {
            ## Find the parameter that applies to this parameter set entry
            $parameter = Get-PsSpecCmdletParameter -ProjectName $projectName -CmdletName $cmdletName `
                -Name $parameterSetEntry.ParameterName -ServiceUri $serviceUri

            ## Clean up the type field for display as done in the default output of Get-Command
            $parameterType = $parameter.Type -replace "System\.Management\.Automation\.",""
            $parameterType = $parameterType -replace "System\.",""

            ## If it is a boolean or switch parameter, generate the command-line syntax for
            ## boolean and switch parameters. Boolean and Switch parameters do not require
            ## a parameter value in the syntax display
            if($parameterType -match "(Bool)|(Switch)")
            {
                if($parameterSetEntry.Mandatory)
                {
                    $syntaxString += " -{0}" -f $parameterSetEntry.ParameterName
                }
                else
                {
                    $syntaxString += " [-{0}]" -f $parameterSetEntry.ParameterName
                }
            }
            else
            {
                ## Check if the position is mandatory or not. Non-mandatory parameters have square
                ## brackets around both their name and value, as that indicates being optional
                if($parameterSetEntry.Mandatory)
                {
                    ## Positional parameters have square brackets around their parameter name, as that
                    ## indicates the parameter name is optional
                    if($parameterSetEntry.Position -is [Int])
                    {
                        $syntaxString += " [-{0}] <{1}>" -f $parameterSetEntry.ParameterName, $parameterType
                    }
                    else
                    {
                        $syntaxString += " -{0} <{1}>" -f $parameterSetEntry.ParameterName, $parameterType
                    }
                }
                else
                {
                    if($parameterSetEntry.Position -is [Int])
                    {
                        $syntaxString += " [[-{0}] <{1}>]" -f $parameterSetEntry.ParameterName, $parameterType
                    }
                    else
                    {
                        $syntaxString += " [-{0} <{1}>]" -f $parameterSetEntry.ParameterName, $parameterType
                    }
                }
            }
        }

        ## Emit the syntax string for this parameter set
        $syntaxString
    }
}

## Generate the table of parameters
function GenerateParameters($projectName, $cmdletName, $serviceUri)
{
    ## Get all of the parameters that apply to the cmdlet
    $parameters = @(Get-PsSpecCmdletParameter -ProjectName $projectName -CmdletName $cmdletName -ServiceUri $serviceUri)

    foreach($parameter in $parameters)
    {
        ## Get all of the parameter set entries that apply to the parameter
        $parameterSetEntries = @(Get-PsSpecCmdletParameterSetEntry -ProjectName $projectName -CmdletName $cmdletName `
            -ParameterName $parameter.Name -ServiceUri $serviceUri)

        ## Format the overview information as HTML
@"
<h3 class="tableHeader">$($parameter.Name) Parameter</h3>
<table cellspacing="0" cellpadding="4">
  <tr>
    <th colspan="2">Parameter Definition</th>
  </tr>
  <tr>
    <td width="20%"><p class="label">Type</p>
        </td>
    <td width="80%"><p class="value">$($parameter.Type)</p></td>
  </tr>
  <tr>
    <td><p class="label">Description</p></td>
    <td><p class="value">$((HtmlEncode $parameter.Description) -replace "`n","<br />`r`n")</p></td>
  </tr>
  <tr>
    <td><p class="label">Notes</p></td>
    <td><p class="value">$((HtmlEncode $parameter.Notes) -replace "`n","<br />`r`n")</p></td>
  </tr>
</table>
<table cellspacing="0" cellpadding="4">
  <tr>
    <th colspan="2">Validation</th>
  </tr>
"@

        ## Format the parameterset-specific validation
        foreach($parameterSetEntry in $parameterSetEntries)
        {
@"

  <tr>
    <td colspan="2"><p class="subheader">$(
        if($parameterSetEntry.ParameterSetName) { "Parameter Set: <i>$($parameterSetEntry.ParameterSetName)</i>" }
        else { "<i>All</i> Parameter Sets" }
    )</p>
  </td>
  </tr>
  <tr>
    <td width="20%"><p class="label">Mandatory</p>
        </td>
    <td width="80%"><p class="value">$($parameterSetEntry.Mandatory)</p></td>
  </tr>
  <tr>
    <td><p class="label">Position</p></td>
    <td><p class="value">$($parameterSetEntry.Position)</p></td>
  </tr>
  <tr>
    <td><p class="label">Takes Value from Pipeline</p></td>
    <td><p class="value">$($parameterSetEntry.ValueFromPipeline)</p></td>
  </tr>
  <tr>
    <td><p class="label">Takes Value from Pipeline by Property Name</p></td>
    <td><p class="value">$($parameterSetEntry.ValueFromPipelineByPropertyName)</p></td>
  </tr>
  <tr>
    <td><p class="label">Takes Value from Remaining Arguments</p></td>
    <td><p class="value">$($parameterSetEntry.ValueFromRemainingArguments)</p></td>
  </tr>
  <tr>
    <td><p class="label">Help Message</p></td>
    <td><p class="value">$($parameterSetEntry.HelpMessage)</p></td>
  </tr>
  <tr>
    <td><p class="label">Help Message BaseName</p></td>
    <td><p class="value">$($parameterSetEntry.HelpMessageBaseName)</p></td>
  </tr>
  <tr>
    <td><p class="label">Help Message Resource Id</p></td>
    <td><p class="value">$($parameterSetEntry.HelpMessageResourceId)</p></td>
  </tr>

"@

        }

        ## Format the detailed validation attributes
@"
</table>
<table cellspacing="0" cellpadding="4">
  <tr>
    <th colspan="2">Detailed Validation Attributes</th>
  </tr>
  <tr>
    <td colspan="2"><p class="subheader">Validation that applies to all parameters</p></td>
  </tr>
  <tr>
    <td width="20%"><p class="label">Allow Null</p>
        </td>
    <td width="80%"><p class="value">$($parameter.AllowNull)</p></td>
  </tr>
  <tr>
    <td><p class="label">Disallow Null<br />
      <span class="realName">[ValidateNotNull]</span></p>
    </td>
    <td><p class="value">$($parameter.ValidateNotNull)</p></td>
  </tr>
  <tr>
    <td colspan="2"><p class="subheader">Validation that applies to string-based parameters</p></td>
  </tr>
  <tr>
    <td><p class="label">Supports Wildcards<br />
      <span class="realName">[AllowGlobbing]</span></p>
    </td>
    <td><p class="value">$($parameter.AllowGlobbing)</p></td>
  </tr>
  <tr>
    <td><p class="label">Supported Values<br />
      <span class="realName">[ValidateSet]</span></p>
    </td>
    <td><p class="value">$(HtmlEncode $($ofs = ", "; $parameter.ValidateSet))</p></td>
  </tr>
  <tr>
    <td><p class="label">Length Limits<br />
      <span class="realName">[ValidateLength]</span></p>
    </td>
    <td><p class="value">$($ofs=" to "; $parameter.ValidateLength)</p></td>
  </tr>
  <tr>
    <td><p class="label">Regular Expression Pattern Validation<br />
      <span class="realName">[ValidatePattern]</span></p>
    </td>
    <td><p class="value">$(HtmlEncode $parameter.ValidatePattern)</p></td>
  </tr>
  <tr>
    <td><p class="label">Disallow Null and Empty<br />
      <span class="realName">[ValidateNotNullOrEmpty]</span></p>
    </td>
    <td><p class="value">$($parameter.ValidateNotNullOrEmpty)</p></td>
  </tr>
  <tr>
    <td><p class="label">Allow Empty String</p></td>
    <td><p class="value">$($parameter.AllowEmptyString)</p></td>
  </tr>
  <tr>
    <td colspan="2"><p class="subheader">Validation that applies to integer-based parameters</p></td>
  </tr>
  <tr>
    <td><p class="label">Range Limits<br />
      <span class="realName">[ValidateRange]</span></p>
    </td>
    <td><p class="value">$($ofs=" to "; $parameter.ValidateRange)</p></td>
  </tr>
  <tr>
    <td colspan="2"><p class="subheader">Validation that applies to collection-based parameters</p></td>
  </tr>
  <tr>
    <td><p class="label">Allow Empty Collection</p>
    </td>
    <td><p class="value">$($parameter.AllowEmptyCollection)</p></td>
  </tr>
  <tr>
    <td><p class="label">Size Limits<br />
      <span class="realName">[ValidateCount]</span></p>
    </td>
    <td><p class="value">$($ofs=" to "; $parameter.ValidateCount)</p></td>
  </tr>
</table>
"@

    }
}

## Encode text so that it is safe to output as HTML content
function HtmlEncode($text)
{
    [System.Web.HttpUtility]::HtmlEncode($text)
}

. Main